using System;
using System.Xml; 

namespace Fadd.Data.Mappings
{
    /// <summary>
    /// Reads a mapping definition from an XML reader.
    /// </summary>
    public class XmlMappingReader
    {
        private readonly XmlTextReader _reader;
        private readonly Type _type;
        private Mapping _mapping;
        private string _propertyName;
        private string _columnName;
        private string _generatorName;


        /// <summary>
        /// Initializes a new instance of the <see cref="XmlMappingReader"/> class.
        /// </summary>
        /// <param name="reader">Reader containing mapping XML.</param>
        /// <param name="type">Type that the mapping is for.</param>
        public XmlMappingReader(XmlTextReader reader, Type type)
        {
            Check.Require(reader, "reader");
            Check.Require(type, "type");

            _reader = reader;
            _type = type;
        }

        private enum ReaderState
        {
            Class,
            PrimaryKey,
            Property
        }

        /// <summary>
        /// Read mapping.
        /// </summary>
        /// <returns>Mapping if found</returns>
        /// <exception cref="MappingException">If mapping is incorrect (syntax).</exception>
        public IMapping Read()
        {
            while (_reader.Read())
            {
                if (_reader.NodeType != XmlNodeType.Element)
                    continue;

                switch (_reader.Name)
                {
                    case "class":
                        while (_reader.MoveToNextAttribute())
                            ProcessClassAttribute();
                        break;
                    case "id":
                        while (_reader.MoveToNextAttribute())
                            ProcessPrimaryKeyAttribute();

                        _mapping.Add(_propertyName, _columnName, true, _generatorName);
                        _propertyName = null;
                        _columnName = null;
                        _generatorName = null;
                        break;
                    case "property":
                        while (_reader.MoveToNextAttribute())
                            ProcessPropertyAttribute();
                        break;
                    default:
                        throw new MappingException(_type, "Unknown mapping element: " + _reader.Name);
                }
            }

            return _mapping;
        }

        /// <summary>
        /// Go through property attributes
        /// </summary>
        /// <exception cref="MappingException"><c>MappingException</c>.</exception>
        private void ProcessPropertyAttribute()
        {
            switch (_reader.Name)
            {
                case "name":
                    _propertyName = _reader.Value;
                    break;
                case "column":
                    _columnName = _reader.Value;
                    break;
                case "type":
                    break; // TODO: implement
                case "length":
                    break; // TODO: implement
                default:
                    throw new MappingException(_type, "Unexpected property attribute: " + _reader.Name);
            }
            if (string.IsNullOrEmpty(_propertyName) || string.IsNullOrEmpty(_columnName)) 
                return;

            if (_mapping == null)
                throw new MappingException(_type, "Class type have not been defined.");

            _mapping.Add(_propertyName, _columnName);
            _propertyName = null;
            _columnName = null;
        }

        /// <exception cref="MappingException"><c>MappingException</c>.</exception>
        private void ProcessPrimaryKeyAttribute()
        {
            switch (_reader.Name)
            {
                case "name":
                    _propertyName = _reader.Value;
                    break;
                case "column":
                    _columnName = _reader.Value;
                    break;
                case "generator":
                    _generatorName = _reader.Value;
                    break;
                case "type":
                    break; // TODO: implement
                case "length":
                    break; // TODO: implement
                default:
                    throw new MappingException(_type, "Unexpected property attribute: " + _reader.Name);
            }

            if (string.IsNullOrEmpty(_propertyName) || string.IsNullOrEmpty(_columnName) ||
                string.IsNullOrEmpty(_generatorName)) 
                return;

            if (_mapping == null)
                throw new MappingException(_type, "Class _type have not been defined.");
        }

        /// <exception cref="MappingException"><c>MappingException</c>.</exception>
        private bool ProcessClassAttribute()
        {
            switch (_reader.Name)
            {
                case "table":
                    _mapping = new Mapping(_type, _reader.Value);
                    break;
                case "name":
                    string classType = _reader.Value;
                    if (Type.GetType(classType, false, false) != _type)
                        return false; // mappings do not match, TODO: Maybe throw an exception?
                    break;
                default:
                    throw new MappingException(_type, "Unexpected class attribute: " + _reader.Name);
            }
            return true;
        }
    }
}
